﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace MvcToolbox
{
    /// <summary>
    /// Class GridExtension
    /// </summary>
    public static class GridExtension
    {
        
        /// <summary>
        /// Grids the specified SGCIB.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TValue">The type of the T value.</typeparam>
        /// <param name="sgcib">The SGCIB.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="id">The id.</param>
        /// <returns>the MVC Grid.</returns>
        public static MvcGrid<TModel, TValue> Grid<TModel, TValue>(this HtmlHelper<TModel> sgcib, IEnumerable<TValue> expression, string id)
        {
            return new MvcGrid<TModel, TValue>(sgcib, expression, id);
        }

    }
    
    /// <summary>
    /// Class MVC Grid
    /// </summary>
    /// <typeparam name="TModel">The type of the T model.</typeparam>
    /// <typeparam name="TValue">The type of the T value.</typeparam>
    public class MvcGrid<TModel, TValue> : IHtmlString, IMvcGrid<TModel, TValue>
    {
        /// <summary>
        /// The SGCIB
        /// </summary>
        private HtmlHelper<TModel> sgcib;

        /// <summary>
        /// The model
        /// </summary>
        private List<TValue> model;

        /// <summary>
        /// The id
        /// </summary>
        private string id;
        private IEnumerable expression;

        /// <summary>
        /// The is ordered
        /// </summary>
        private bool isOrdered;
        
        /// <summary>
        /// The order field
        /// </summary>
        /// 
        private string orderField;

        /// <summary>
        /// The order direction
        /// </summary>
        private string orderDirection;


        /// <summary>
        /// The is paging
        /// </summary>
        private bool isPaging;
        /// <summary>
        /// The current page
        /// </summary>
        private int currentPage = 1;
        /// <summary>
        /// The number of item count
        /// </summary>
        private int numberOfItemCount = 500;
        /// <summary>
        /// The item per page
        /// </summary>
        private int itemPerPage = 25;

        /// <summary>
        /// Initializes a new instance of the <see cref="MvcGrid{TModel, TValue}"/> class.
        /// </summary>
        /// <param name="sgcib">The SGCIB Helper.</param>
        /// <param name="model">The model.</param>
        /// <param name="id">The id.</param>
        public MvcGrid(HtmlHelper<TModel> sgcib, IEnumerable<TValue> model, string id)
        {
            this.sgcib = sgcib;
            this.model = model as List<TValue>;
            this.id = id;
            this.ColumnsDefinition = new List<GridColumn<TModel, TValue>>();
            this.ViewData = new ViewDataDictionary<TValue>();
        }

        /// <summary>
        /// Gets or sets the columns definition.
        /// </summary>
        /// <value>The columns definition.</value>
        public List<GridColumn<TModel, TValue>> ColumnsDefinition { get; set; }

        /// <summary>
        /// Gets or sets the column.
        /// </summary>
        /// <value>The column.</value>
        public GridColumn<TModel, TValue> Column { get; set; }

        /// <summary>
        /// Gets or sets the view data.
        /// </summary>
        /// <value>The view data.</value>
        public ViewDataDictionary<TValue> ViewData { get; set; }

        /// <summary>
        /// Columns the specified configurator.
        /// </summary>
        /// <param name="configurator">The configurator.</param>
        /// <returns>the MVC Grid.</returns>
        public MvcGrid<TModel, TValue> Columns(Action<GridColumnFactory<TModel, TValue>> configurator)
        {
            GridColumnFactory<TModel, TValue> factory = new GridColumnFactory<TModel, TValue>(this);
            configurator(factory);
            return this;
        }

        public MvcGrid<TModel, TValue> Ordered(string field, string direction)
        {
            this.isOrdered = true;
            this.orderField = field;
            this.orderDirection = direction;
            return this;
        }

        public MvcGrid<TModel, TValue> Paging(int currentPage, int numberOfItemCount, int itemPerPage)
        {
            this.isPaging = true;
            this.currentPage = currentPage;
            this.numberOfItemCount = numberOfItemCount;
            this.itemPerPage = itemPerPage;
            return this;
        }

        /// <summary>
        /// Returns an HTML-encoded string.
        /// </summary>
        /// <returns>An HTML-encoded string.</returns>
        public string ToHtmlString()
        {
            int _orderDirection = 1;
            if (orderDirection == "DESC")
                _orderDirection = 2;

            var html = this.sgcib;
            StringBuilder control = new StringBuilder();
            control.Append(@"<div class='n-grid' id='" + this.id + @"'>
                            <div class='grid'>
                                <table pageIndex=" + currentPage + " pageSize=" + itemPerPage + @" propertyName='" + orderField + @"' direction='" + _orderDirection + @"'>
                                  <thead>
                                    <tr>");

            this.RenderHeader(control);
            control.Append("</thead><tbody>");
            for (int j = 0; j < this.model.Count; j++)
            {
                string cssClass = j % 2 == 0 ? string.Empty : "class=n-alt";
                control.Append(@"<tr " + cssClass + @">");

                var tmpModel = this.model[j];
                ModelMetadata metaData = ModelMetadataProviders.Current.GetMetadataForType(() => this.model[j], typeof(TValue));
                ViewDataDictionary viewData = new ViewDataDictionary(tmpModel);
                this.RenderLine(control, tmpModel, metaData, viewData);

                control.Append(@"</tr>");
            }

            control.Append(@"</tbody></table></div>");
            if (isPaging)
            {
                DrawPager(control);
            }
            control.Append(@"</div>");
            return control.ToString();
        }

        private void DrawPager(StringBuilder control)
        {
            int startPage = ((int)((currentPage-1)/10))*10+1;
            int firstItem = ((currentPage - 1) * itemPerPage) + 1;
            int lastDisplayPage = startPage+9;
            int totalPage = (int)(numberOfItemCount / itemPerPage);
            if (numberOfItemCount % itemPerPage > 0)
            {
                totalPage++;
            }
            if ( lastDisplayPage > totalPage)
            {
                lastDisplayPage = totalPage;
            }
            int previousPage = currentPage - 1;
            if (previousPage <= 1)
                previousPage = 1;
            control.Append(@"
            <div class='n-pager-wrap n-grid-pager n-widget' data-role='pager'>
                <a href='#' title='Go to the first page' class='n-link n-state-disabled' data-page='1' tabindex='-1'>
                    <span class='n-icon n-i-seek-w'>Go to the first page</span>
                </a>
                <a href='#' title='Go to the previous page' class='n-link n-state-disabled' data-page='" + previousPage + @"' tabindex='-1'>
                    <span class='n-icon n-i-arrow-w'>Go to the previous page</span>
                </a>
                <ul class='n-pager-numbers n-reset'>");
                if (startPage > 1)
                {
                    control.Append(@"<li><a tabindex='-1' href='#' class='n-link' data-page='" + (startPage - 1) + "'>...</a></li>");
                }
                for(int i = startPage; i <= lastDisplayPage; i++)
                {
                    if (i != currentPage)
                    {
                        control.Append(@"<li><a href='#' class='n-link' data-page='" + i + "'>" + i + "</a></li>");
                    }
                    else
                    {
                        control.Append(@"<li><span class='n-link selected'>"+i+"</span></li>");
                    }
                }
                if (lastDisplayPage < totalPage)
                {
                    control.Append(@"<li><a tabindex='-1' href='#' class='n-link' data-page='" + (lastDisplayPage + 1) + "'>...</a></li>");
                }
                int _nextPage = currentPage + 1;
                if (_nextPage >= totalPage)
                {
                    _nextPage = totalPage;
                }
                control.Append(@"</ul>
                <a href='#' title='Go to the next page' class='n-link " + _nextPage + @"' data-page='" + (_nextPage) + @"'><span class='n-icon n-i-arrow-e'>Go to the next page</span></a>
                <a href='#' title='Go to the last page' class='n-link' data-page='" + totalPage + @"'>
                    <span class='n-icon n-i-seek-e'>Go to the last page</span>
                </a>
                <span class='n-pager-info n-label'>" + firstItem + " - " + (firstItem + itemPerPage) + " of " + numberOfItemCount + @" items</span>
            </div>");
        }

        /// <summary>
        /// Renders the header.
        /// </summary>
        /// <param name="control">The control.</param>
        private void RenderHeader(StringBuilder control)
        {
            int _orderDirection = 1;
            if (orderDirection == "DESC")
                _orderDirection = 2;

            if (this.ColumnsDefinition.Count > 0)
            {
                foreach (var cols in this.ColumnsDefinition)
                {
                    string spanOrder = "";
                    string defaultClass = string.Empty;
                    if (isOrdered && !cols.NoOrder)
                    {
                        if (cols.PropertyName == orderField)
                        {
                            spanOrder = "<span class='order " + orderDirection + "' propertyName=" + cols.PropertyName + " direction=" + _orderDirection + "></span>";
                        }
                        else
                        {
                            spanOrder = "<span class='order'propertyName=" + cols.PropertyName + " direction=0></span>";
                        }
                        defaultClass = " class='order' ";
                    }
                    string style = cols.IsHide ? " class='hide' " : defaultClass;
                    control.Append(@"<th" + style + ">" + cols.Header + spanOrder + "</th>");
                }
            }
            else
            {
                foreach (var displayName in this.GetColumnsName())
                {
                    control.Append(@"<th>" + displayName + "</th>");
                }
            }

            control.Append(@"</tr>");
        }

        /// <summary>
        /// Gets the name of the columns.
        /// </summary>
        /// <returns>Column name</returns>
        private List<string> GetColumnsName()
        {
            List<string> columnsName = new List<string>();
            foreach (var propertyType in typeof(TValue).GetProperties())
            {
                DisplayNameAttribute attributes = (DisplayNameAttribute)propertyType.GetCustomAttributes(true).OfType<DisplayNameAttribute>().SingleOrDefault();
                columnsName.Add(attributes.DisplayName);
            }

            return columnsName;
        }

        /// <summary>
        /// Renders the line.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <param name="tmpModel">The TMP model.</param>
        /// <param name="metaData">The meta data.</param>
        /// <param name="viewData">The view data.</param>
        private void RenderLine(StringBuilder control, TValue tmpModel, ModelMetadata metaData, ViewDataDictionary viewData)
        {
            var html = this.sgcib;
            foreach (var item in this.ColumnsDefinition)
            {
                var prop = metaData.Properties.Where(pm => pm.PropertyName == item.PropertyName).SingleOrDefault();
                if (prop != null)
                {
                    item.Render(control, html, prop, metaData);
                }
                else if (prop == null && item.GetType().Name.Contains("GridColumn"))
                {
                    item.Render(control, html, prop, null);
                }
            }
        }
    }
}